# coding=utf-8
import pygame
from pygame.math import Vector2
from typing import Union, Dict
from sound import Sound


class InResultEnum:
  NULL = 'NULL'
  """没用"""
  OCCLUSION = 'OCCLUSION'
  """遮挡"""
  PENETRATE = 'PENETRATE'
  """穿透"""


allUiNode = {}
allFunc = {}


mousePos = (0.0, 0.0)


def RegisterUiNode(cls):
  print('RegisterUiNode=', cls.__name__)
  allUiNode[cls.__name__] = cls
  return cls


def RegisterFunc(func):
  print('RegisterFunc=', func.__name__)
  allFunc[func.__name__] = func
  return func


@RegisterUiNode
class Panel:
  def __init__(
      self, sourcePanel: 'Panel' = None, pos=(0, 0), size=(100, 100), rot=0.0, visible=True, layer=0,
      childPanelDict=None,
      inResult=InResultEnum.NULL, canDrag=False, scale=1.0
  ):
    self.sourcePanel = sourcePanel
    self._surfaceTemp = None  # type: Union[None, pygame.Surface]
    self._pos = pos
    self._size = size
    self._visible = visible
    self.layer = layer
    self.scale = scale
    self._rot = rot
    self.inResult = inResult
    self.canDrag = canDrag
    if self.canDrag:
      self.inResult = InResultEnum.PENETRATE
    self.childPanel = {}  # type: Dict[str, Union['Panel', Text, Img, Btn]]
    self.LoadChildPanel(childPanelDict)
    self._readyMove = (0, 0)

  @property
  def visible(self):
    return self._visible

  @visible.setter
  def visible(self, value):
    if self.visible == value:
      return
    self._visible = value
    self.ClearSurfaceTemp()
    # self.ClearChildNodeSurfaceTemp()

  def ClearSurfaceTemp(self):
    # if self._surfaceTemp is None:
    #   return
    self._surfaceTemp = None
    if self.sourcePanel is not None:
      self.sourcePanel.ClearSurfaceTemp()

  def ClearChildNodeSurfaceTemp(self):
    self._surfaceTemp = None
    for node in self.childPanel.values():
      node.ClearChildNodeSurfaceTemp()

  def AddChildNode(self, nodeName, node: 'Panel'):
    self.childPanel[nodeName] = node
    node.sourcePanel = self
    self.ClearSurfaceTemp()

  def RemoveChildNode(self, nodeName):
    if nodeName not in self.childPanel:
      return
    self.childPanel[nodeName].sourcePanel = None
    self.childPanel.pop(nodeName)
    self.ClearSurfaceTemp()

  @property
  def pos(self):
    return self._pos

  @pos.setter
  def pos(self, value):
    if self.pos == value:
      return
    self._pos = value
    self.ClearSurfaceTemp()

  @property
  def size(self):
    return self._size

  @property
  def rot(self):
    return self._rot

  @rot.setter
  def rot(self, value):
    self._rot = value
    self.ClearSurfaceTemp()

  @property
  def rect(self):
    return pygame.transform.rotate(pygame.Surface(self.size), self.rot).get_rect()

  def _ReadyMove(self):
    # self._readyMove = pygame.mouse.get_pos()
    self._readyMove = mousePos

  def _StopMove(self):
    self._readyMove = (0, 0)

  def _MoveSelf(self):
    if self._readyMove[0] != 0 or self._readyMove[1] != 0:
      # mousePos = pygame.mouse.get_pos()
      self.pos = (
        self.pos[0] + mousePos[0] - self._readyMove[0],
        self.pos[1] + mousePos[1] - self._readyMove[1]
      )
      self._readyMove = mousePos

  def SortedChildPanels(self):
    return sorted(self.childPanel.values(), key=lambda p: p.layer)

  def ToLocalPos(self, pos):
    return pos[0] - self.pos[0], pos[1] - self.pos[1]

  def GetPanelsBySource(self):
    """获得路径上的所有panel实例，从根到叶"""
    panels = [self]
    while panels[-1].sourcePanel is not None:
      panels.append(panels[-1].sourcePanel)
    return panels[::-1]

  def CollidePoint(self, pos):
    if pos[0] < 0 or pos[0] > self.size[0]:
      return False
    if pos[1] < 0 or pos[1] > self.size[1]:
      return False
    return True

  def RotateAfterSize(self):
    if self._surfaceTemp is None:
      return pygame.transform.rotate(pygame.Surface(self.size), self.rot).get_size()
    return self._surfaceTemp.get_size()

  def PosToLocal(self, pos):
    # 还原缩放
    pos = (Vector2(pos) - Vector2(self.pos)) / self.scale
    # 将坐标位移到以本图形中心为圆心的坐标系
    pos = pos - Vector2(self.RotateAfterSize()) * 0.5
    # 围绕圆心逆旋转
    pos = pos.rotate(self.rot)
    # 由于pygame的旋转会放大图形，因此此时的图形中心会发生 原来半径*实际半径/原来半径 的位移
    pos = pos + Vector2(self.size) * 0.5
    return pos

  def In(self, pos, event: pygame.event.Event):
    if not self.visible:
      return InResultEnum.NULL
    pos = self.PosToLocal(pos)
    for panel in self.SortedChildPanels():
      if panel.In(pos, event) == InResultEnum.OCCLUSION:
        return InResultEnum.OCCLUSION
    if self.inResult != InResultEnum.NULL and self.CollidePoint(pos):
      self.OnIn(event)
      return self.inResult
    self.OnOut(event)
    return InResultEnum.NULL

  def OnOut(self, event: pygame.event.Event):
    if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
      if self.canDrag:
        self._StopMove()
    if event.type == pygame.MOUSEMOTION:
      if self.canDrag:
        self._MoveSelf()

  def OnIn(self, event: pygame.event.Event):
    if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
      if self.canDrag:
        self._ReadyMove()
    if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
      if self.canDrag:
        self._StopMove()
    if event.type == pygame.MOUSEMOTION:
      if self.canDrag:
        self._MoveSelf()

  def OnDraw(self, drawSurface: pygame.Surface):
    pass

  def Draw(self, drawSurface: pygame.Surface):
    if not self.visible:
      return
    if self._surfaceTemp is not None:
      drawSurface.blit(self._surfaceTemp, self.pos)
      return
    surface = pygame.Surface(self.size, pygame.SRCALPHA)
    self.OnDraw(surface)
    for panel in self.SortedChildPanels():
      panel.Draw(surface)
    if self.scale != 1.0:
      surface = pygame.transform.scale(surface, (self.size[0] * self.scale, self.size[1] * self.scale))
    if self.rot != 0.0:
      surface = pygame.transform.rotate(surface, self.rot)
    drawSurface.blit(surface, self.pos)
    self._surfaceTemp = surface

  def LoadChildPanel(self, childPanelDict):
    if childPanelDict is None:
      return
    for key, value in childPanelDict.items():
      keys = key.split('@')
      self.AddChildNode(keys[0], allUiNode[keys[-1]](sourcePanel=self, **value))


class TextAlignEnum:
  LEFT = 'LEFT'
  """左对齐"""
  RIGHT = 'RIGHT'
  """右对齐"""
  CENTER = 'CENTER'
  """中央对齐"""


@RegisterUiNode
class Text(Panel):
  def __init__(
      self, text='', textSize=16, textFont='font/bb.ttf', textColor=(255, 255, 255), textAlign=TextAlignEnum.CENTER,
      textShadow=False,
      sourcePanel=None, pos=(0, 0), size=(100, 100),
      rot=0.0, visible=True, layer=0, childPanelDict=None,
      inResult=InResultEnum.NULL, canDrag=False, scale=1.0
  ):
    super().__init__(sourcePanel, pos, size, rot, visible, layer, childPanelDict, inResult, canDrag, scale)
    self._text = text
    self.textShadow = textShadow
    self._textSize = textSize
    self.textColor = textColor
    self.textAlign = textAlign
    self.textFontName = textFont
    self.textFont = pygame.font.Font(textFont, self.textSize)

  @property
  def text(self):
    return self._text

  @text.setter
  def text(self, value):
    if self._text == value:
      return
    self._text = value
    self.ClearSurfaceTemp()

  @property
  def textSize(self):
    return self._textSize

  @textSize.setter
  def textSize(self, value):
    if self.textSize == value:
      return
    self._textSize = value
    self.textFont = pygame.font.Font(self.textFontName, self.textSize)
    self.ClearSurfaceTemp()

  def OnDraw(self, drawSurface: pygame.Surface):
    if self.textShadow:
      textSurface = self.textFont.render(self.text, True, (0, 0, 0))
      shadowWidth = 2
      if self.textAlign == TextAlignEnum.LEFT:
        drawSurface.blit(textSurface, (shadowWidth, (self.size[1] - textSurface.get_height()) * 0.5 + shadowWidth))
      elif self.textAlign == TextAlignEnum.RIGHT:
        drawSurface.blit(textSurface, (
          self.size[0] - textSurface.get_width() + shadowWidth,
          (self.size[1] - textSurface.get_height()) * 0.5 + shadowWidth
        ))
      else:
        drawSurface.blit(textSurface, (
          (self.size[0] - textSurface.get_width()) * 0.5 + shadowWidth,
          (self.size[1] - textSurface.get_height()) * 0.5 + shadowWidth
        ))
    textSurface = self.textFont.render(self.text, True, self.textColor)
    if self.textAlign == TextAlignEnum.LEFT:
      drawSurface.blit(textSurface, (0, (self.size[1] - textSurface.get_height()) * 0.5))
    elif self.textAlign == TextAlignEnum.RIGHT:
      drawSurface.blit(textSurface, (
        self.size[0] - textSurface.get_width(),
        (self.size[1] - textSurface.get_height()) * 0.5
      ))
    else:
      drawSurface.blit(textSurface, (
        (self.size[0] - textSurface.get_width()) * 0.5,
        (self.size[1] - textSurface.get_height()) * 0.5
      ))


@RegisterUiNode
class Img(Panel):
  def __init__(
      self, imgPath='', imgColor=None, imgScaleZoom=True, imgGrid=(0, 0, 0, 0),
      sourcePanel=None, pos=(0, 0), size=(100, 100),
      rot=0.0, visible=True, layer=0, childPanelDict=None,
      inResult=InResultEnum.NULL, canDrag=False, scale=1.0
  ):
    super().__init__(sourcePanel, pos, size, rot, visible, layer, childPanelDict, inResult, canDrag, scale)
    self.imgPath = imgPath
    if imgPath != '':
      self.img = pygame.image.load(imgPath).convert_alpha()
    else:
      self.img = pygame.Surface(self.size, pygame.SRCALPHA)
    self.imgColor = imgColor
    self.imgGrid = imgGrid
    self.imgScaleZoom = imgScaleZoom
    if sum(imgGrid) > 0:
      self.RedrawImg()
    if imgColor is not None:
      self.RedrawColor()
    if imgScaleZoom:
      self.img = pygame.transform.scale(self.img, self.size)

  def SetImg(self, newImg):
    self.img = newImg
    if sum(self.imgGrid) > 0:
      self.RedrawImg()
    if self.imgColor is not None:
      self.RedrawColor()
    if self.imgScaleZoom:
      self.img = pygame.transform.scale(self.img, self.size)
    self.ClearSurfaceTemp()

  def OnDraw(self, drawSurface: pygame.Surface):
    drawSurface.blit(self.img, (0, 0))

  def RedrawImg(self):
    surface = pygame.Surface(self.size, pygame.SRCALPHA)
    t, d, l, r = self.imgGrid
    x, y = self.img.get_size()
    # x -= 1
    # y -= 1
    # print('t, d, l, r=', t, d, l, r)
    # print('x, y=', x, y)
    # 提取四个角
    leftTopSurface = self.img.subsurface(0, 0, l, t)  # x1,y1,x2,y2
    leftDownSurface = self.img.subsurface(0, y - d, l, d)
    rightTopSurface = self.img.subsurface(x - r, 0, r, t)  # x1,y1,x2,y2
    rightDownSurface = self.img.subsurface(x - r, y - d, r, d)
    # 提取四条边
    topSurface = self.img.subsurface(l, 0, x - r, t)
    downSurface = self.img.subsurface(l, y - d, x - r - l, d)
    leftSurface = self.img.subsurface(0, t, l, y - d)
    rightSurface = self.img.subsurface(x - r, t, r, y - d - t)
    # 提取中心
    centerSurface = self.img.subsurface(l, t, x - r - l, y - d - t)
    x2, y2 = self.size
    # x2 -= 1
    # y2 -= 1
    # 绘制四角
    surface.blit(leftTopSurface, (0, 0))
    surface.blit(leftDownSurface, (0, y2 - d))
    surface.blit(rightTopSurface, (x2 - r, 0))
    surface.blit(rightDownSurface, (x2 - r, y2 - d))
    # 拉升四边
    topSurface = pygame.transform.scale(topSurface, (x2 - r - l, t))
    downSurface = pygame.transform.scale(downSurface, (x2 - r - l, d))
    leftSurface = pygame.transform.scale(leftSurface, (l, y2 - d - t))
    rightSurface = pygame.transform.scale(rightSurface, (r, y2 - d - t))
    # 绘制四边
    surface.blit(topSurface, (l, 0))
    surface.blit(downSurface, (l, y2 - d))
    surface.blit(leftSurface, (0, t))
    surface.blit(rightSurface, (x2 - r, t))
    # 拉升中心
    centerSurface = pygame.transform.scale(centerSurface, (x2 - r - l, y2 - d - t))
    # 绘制中心
    surface.blit(centerSurface, (l, t))
    self.img = surface

  def RedrawColor(self):
    for x in range(self.img.get_width()):
      for y in range(self.img.get_height()):
        # 获取当前像素的颜色值
        pixelColor = self.img.get_at((x, y))
        # 检查当前像素是否透明
        if pixelColor[3] > 0:
          # 应用颜色滤波器，同时保持透明度
          newColor = (self.imgColor[0], self.imgColor[1], self.imgColor[2], pixelColor[3])
          self.img.set_at((x, y), newColor)


def GetFunc(funcName):
  if type(funcName) == str:
    return allFunc[funcName]
  return funcName


@RegisterUiNode
class Btn(Panel):
  def __init__(
      self, text='', textSize=16, textColor=(255, 255, 255), textFont='font/bb.ttf',
      downFunc=None, moveFunc=None, upFunc=None, imgGrid=(0, 0, 0, 0), imgColor=None,
      defImgPath='texture/btn_def.png', hoverImgPath='texture/btn_hover.png', pressImgPath='texture/btn_press.png',
      sourcePanel=None, pos=(0, 0), size=(100, 100),
      rot=0.0, visible=True, layer=0, childPanelDict=None, textShadow=True,
      inResult=InResultEnum.OCCLUSION, canDrag=False, scale=1.0, downSound='sound/btndown_02.wav',
  ):
    super().__init__(sourcePanel, pos, size, rot, visible, layer, childPanelDict, inResult, canDrag, scale)
    self.AddChildNode(
      'text',
      Text(sourcePanel=self, size=self.size, text=text, textSize=textSize, textFont=textFont, textColor=textColor, layer=1, textShadow=textShadow)
    )
    self.AddChildNode(
      'def_img',
      Img(sourcePanel=self, size=self.size, imgPath=defImgPath, imgGrid=imgGrid, imgColor=imgColor)
    )
    self.AddChildNode(
      'hover_img',
      Img(sourcePanel=self, size=self.size, imgPath=hoverImgPath, visible=False, imgGrid=imgGrid)
    )
    self.AddChildNode(
      'press_img',
      Img(sourcePanel=self, size=self.size, imgPath=pressImgPath, visible=False, imgGrid=imgGrid)
    )
    self.downFunc = GetFunc(downFunc)
    self.moveFunc = GetFunc(moveFunc)
    self.upFunc = GetFunc(upFunc)
    if downSound == '':
      self.downSound = None
    else:
      self.downSound = Sound(downSound)

  @property
  def text(self):
    return self.childPanel['text'].text

  @text.setter
  def text(self, value):
    if self.text == value:
      return
    self.childPanel['text'].text = value
    self.ClearSurfaceTemp()

  def Click(self):
    if self.downFunc is not None:
      self.downFunc(self)
    if self.upFunc is not None:
      self.upFunc(self)

  def OnIn(self, event: pygame.event.Event):
    # print('OnIn')
    Panel.OnIn(self, event)
    if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
      self.childPanel['def_img'].visible = False
      self.childPanel['hover_img'].visible = False
      self.childPanel['press_img'].visible = True
      # if self.downSound is not None:
      #   self.downSound.play()
      if self.downFunc is not None:
        self.downFunc(self)
      self.ClearSurfaceTemp()
    if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
      self.childPanel['def_img'].visible = True
      self.childPanel['hover_img'].visible = False
      self.childPanel['press_img'].visible = False
      if self.downSound is not None:
        self.downSound.play()
      if self.upFunc is not None:
        self.upFunc(self)
      self.ClearSurfaceTemp()
    if event.type == pygame.MOUSEMOTION:
      self.childPanel['def_img'].visible = False
      self.childPanel['hover_img'].visible = True
      self.childPanel['press_img'].visible = False
      if self.moveFunc is not None:
        self.moveFunc(self)
      self.ClearSurfaceTemp()

  def OnOut(self, event: pygame.event.Event):
    Panel.OnOut(self, event)
    if event.type == pygame.MOUSEMOTION:
      self.childPanel['def_img'].visible = True
      self.childPanel['hover_img'].visible = False
      self.childPanel['press_img'].visible = False
      self.ClearSurfaceTemp()
